using System;

namespace Gert3D
{
  /// <summary>
  /// Vector defines a three dimensional Vector with all standard vector math routines.
  /// </summary>
  public class Vector3
  {
    /// <summary>
    /// The internal field for the x component of the vector.
    /// </summary>
    public double _x = 0.0f;
    /// <summary>
    /// The internal field for the y component of the vector.
    /// </summary>
    public double _y = 0.0f;
    /// <summary>
    /// The internal field for the z component of the vector.
    /// </summary>
    public double _z = 0.0f;


    /// <summary>
    /// Creates a new Vector with X, Y and Z set to 0.
    /// </summary>
    public Vector3()
    {
    }

    /// <summary>
    /// Creates a new Vector and initializes X and Y with the given values.
    /// </summary>
    /// <param name="x">The x component.</param>
    /// <param name="y">The y component.</param>
    /// <param name="z">The z component.</param>
    public Vector3(double x, double y, double z)
    {
      _x = x;
      _y = y;
      _z = z;
    }

    /// <summary>
    /// Gets the X component of the vector.
    /// </summary>
    public double X
    {
      get { return _x; }
      set { _x = value; }
    }

    /// <summary>
    /// Gets the Y component of this vector.
    /// </summary>
    public double Y
    {
      get { return _y; }
      set { _y = value; }
    }

    /// <summary>
    /// Gets the Z component of this vector.
    /// </summary>
    public double Z
    {
      get { return _z; }
      set { _z = value; }
    }

    /// <summary>
    /// Gets the magnitude (length) of the vector by using the pythogarean theorem.
    /// </summary>
    public double Magnitude
    {
      get { return Math.Sqrt(_x * _x + _y * _y + _z * _z); }
    }

    /// <summary>
    /// Adds the values of the given vector to this vector.
    /// </summary>
    /// <param name="other">The vector to be added.</param>
    public void Add(Vector3 other)
    {
      _x += other._x;
      _y += other._y;
      _z += other._z;
    }

    /// <summary>
    /// Subtracts the values of the given vector from this vector.
    /// </summary>
    /// <param name="other">The vector to be subtracted.</param>
    public void Subtract(Vector3 other)
    {
      _x -= other._x;
      _y -= other._y;
      _z -= other._z;
    }

    /// <summary>
    /// Normalizes this Vector. That is, X, Y and Z are scaled so the magnitude of the vector is 1.0.
    /// </summary>
    public void Normalize()
    {
      double magnitude = Magnitude;
      if (magnitude > 0.0f)
      {
        _x /= magnitude;
        _y /= magnitude;
        _z /= magnitude;
      }
    }

    /// <summary>
    /// Scales the components of this vector by the given factor.
    /// </summary>
    /// <param name="factor">The scaling factor.</param>
    public void Scale(double factor)
    {
      _x *= factor;
      _y *= factor;
      _z *= factor;
    }

    /// <summary>
    /// Calculates the dot/scalar product of this and the given vector.
    /// NOTE: The vectors have to normalized!
    /// </summary>
    /// <param name="other">The other vector.</param>
    /// <returns>The dot/scalar product of the two vectors.</returns>
    public double DotProduct(Vector3 other)
    {
      return _x * other._x + _y * other._y + _z * other._z;
    }

    /// <summary>
    /// Calculates the cross product of the vector and the given.
    /// What is calculated is "this x other".
    /// </summary>
    /// <param name="other">The other vector.</param>
    /// <returns>The cross product of the two vectors as a new vector.</returns>
    public Vector3 CrossProduct(Vector3 other)
    {
      return new Vector3(_y * other._z - _z * other._y, _z * other._x - _x * other._z, _x * other._y - _y * other._x);
    }

    /// <summary>
    /// Inverts the direction of the vector.
    /// </summary>
    public void Invert()
    {
      _x *= -1f;
      _y *= -1f;
      _z *= -1f;
    }

    /// <summary>
    /// Reflects the vector by the given surface normal.
    /// </summary>
    /// <param name="normal">The normal to reflect the vector by. Has to be normalized.</param>
    public void Reflect(Vector3 normal)
    {
      double dot = this.DotProduct(normal);
      _x -= 2 * normal._x * dot;
      _y -= 2 * normal._y * dot;
      _z -= 2 * normal._z * dot;
    }

    /// <summary>
    /// Calculates the distance between this vector and another by using pythogarean theorem.
    /// </summary>
    /// <param name="other">The other vector.</param>
    /// <returns>The distance between the two vectors.</returns>
    public double DistanceTo(Vector3 other)
    {
      double a = _x - other._x;
      double b = _y - other._y;
      double c = _z - other._z;
      return Math.Sqrt(a * a + b * b + c * c);
    }

    /// <summary>
    /// Multiplies the components of this vector by the components of the given vector.
    /// </summary>
    /// <param name="other">The vector to multiply by.</param>
    public void Multiply(Vector3 other)
    {
      _x *= other._x;
      _y *= other._y;
      _z *= other._z;
    }

    /// <summary>
    /// Adds the components of the two given vectors and returns the result.
    /// </summary>
    /// <param name="vec1">The first vector.</param>
    /// <param name="vec2">The second vector.</param>
    /// <returns>A new vector with the result of the addition.</returns>
    public static Vector3 Add(Vector3 vec1, Vector3 vec2)
    {
      return new Vector3(vec1._x + vec2._x, vec1._y + vec2._y, vec1._z + vec2._z);
    }

    /// <summary>
    /// Subtracts the components of the two given vectors and returns the result.
    /// </summary>
    /// <param name="vec1">The first vector.</param>
    /// <param name="vec2">The second vector.</param>
    /// <returns>A new vector with the result of the subtraction.</returns>
    public static Vector3 Subtract(Vector3 vec1, Vector3 vec2)
    {
      return new Vector3(vec1._x - vec2._x, vec1._y - vec2._y, vec1._z - vec2._z);
    }

    /// <summary>
    /// Normalizes the given vector and returns the result as a new vector.
    /// </summary>
    /// <param name="vec">The vector to be normalized.</param>
    /// <returns>The normalized vector.</returns>
    public static Vector3 Normalize(Vector3 vec)
    {
      double magnitude = vec.Magnitude;
      if (magnitude > 0.0f)
        return new Vector3(vec._x / magnitude, vec._y / magnitude, vec._z / magnitude);
      else
        return new Vector3(vec._x, vec._y, vec._z);
    }

    /// <summary>
    /// Scales the components of given vector by the given factor and returns the result as a new vector.
    /// </summary>
    /// <param name="vec">The vector to be scaled.</param>
    /// <param name="factor">The scaling factor.</param>
    /// <returns>The scaled vector.</returns>
    public static Vector3 Scale(Vector3 vec, double factor)
    {
      return new Vector3(vec._x * factor, vec._y * factor, vec._z * factor);
    }

    /// <summary>
    /// Calculates the dot/scalar product of the two given vectors.
    /// NOTE: The vectors have to be normalized!
    /// </summary>
    /// <param name="vec1">The first vector.</param>
    /// <param name="vec2">The second vector.</param>
    /// <returns>The dot/scalar product of the two given vectors.</returns>
    public static double DotProduct(Vector3 vec1, Vector3 vec2)
    {
      return vec1._x * vec2._x + vec1._y * vec2._y + vec1._z * vec2._z;
    }

    /// <summary>
    /// Calculates the cross product of the two given vectors.
    /// What is calculated is "vec1 x vec2".
    /// </summary>
    /// <param name="vec1">The first vector.</param>
    /// <param name="vec2">The second vector.</param>
    /// <returns>The cross product of the two vectors as a new vector.</returns>
    public static Vector3 CrossProduct(Vector3 vec1, Vector3 vec2)
    {
      return new Vector3(vec1._y * vec2._z - vec1._z * vec2._y, vec1._z * vec2._x - vec1._x * vec2._z, vec1._x * vec2._y - vec1._y * vec2._x);
    }

    /// <summary>
    /// Creates a new vector pointing from p1 to p2. The returned vector is not normalized.
    /// </summary>
    /// <param name="p1">The "start" point.</param>
    /// <param name="p2">The "direction" point.</param>
    /// <returns>A new vector pointing from p1 to p2.</returns>
    public static Vector3 FromPoints(Vector3 p1, Vector3 p2)
    {
      return new Vector3(p2.X - p1.X, p2.Y - p1.Y, p2.Z - p1.Z);
    }

    /// <summary>
    /// Inverts the given vector and returns the result as a new vector.
    /// </summary>
    /// <param name="vec">The vector to be inverted.</param>
    /// <returns>The inverted vector.</returns>
    public static Vector3 Invert(Vector3 vec)
    {
      return new Vector3(vec._x * -1f, vec._y * -1f, vec._z * -1f);
    }

    /// <summary>
    /// Reflects the given vector by the given surface normal.
    /// </summary>
    /// <param name="vec">The vector to be reflected.</param>
    /// <param name="normal">The surface normal. Has to be noramlized.</param>
    /// <returns>A new vector that is the result of the reflection.</returns>
    public static Vector3 Reflect(Vector3 vec, Vector3 normal)
    {
      double dot = vec.DotProduct(normal) * 2;
      return new Vector3(vec._x - normal._x * dot, vec._y - normal._y * dot, vec._z - normal._z * dot);
    }

    /// <summary>
    /// Calculates the half vector, which is the vector at the half angle 
    /// between the two given vectors.
    /// </summary>
    /// <param name="vec1">The first vector.</param>
    /// <param name="vec2">The second vector.</param>
    /// <returns>The calculated half vector.</returns>
    public static Vector3 HalfVector(Vector3 vec1, Vector3 vec2)
    {
      return Vector3.Normalize(Vector3.Add(vec1, vec2));
    }

    /// <summary>
    /// Multiplies the components of the two vectors.
    /// </summary>
    /// <param name="vec1">The vector that is multiplied.</param>
    /// <param name="vec2">The vector to multiply by.</param>
    public Vector3 Multiply(Vector3 vec1, Vector3 vec2)
    {
      return new Vector3(
        vec1._x * vec2._x,
        vec1._y * vec2._y,
        vec1._z * vec2._z);
    }

    public override string ToString()
    {
      return "(" + _x + "," + _y + "," + _z + ")";
    }

  }
}
